/*
 * Copyright (C) 2007 SQL Explorer Development Team http://sourceforge.net/projects/eclipsesql
 * 
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General
 * Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package net.sourceforge.sqlexplorer.plugin.editors;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;

import net.sourceforge.sqlexplorer.IConstants;
import net.sourceforge.sqlexplorer.Messages;
import net.sourceforge.sqlexplorer.connections.ConnectionsView;
import net.sourceforge.sqlexplorer.connections.SessionEstablishedAdapter;
import net.sourceforge.sqlexplorer.dbproduct.SQLConnection;
import net.sourceforge.sqlexplorer.dbproduct.Session;
import net.sourceforge.sqlexplorer.dbproduct.User;
import net.sourceforge.sqlexplorer.plugin.SQLExplorerPlugin;
import net.sourceforge.sqlexplorer.sessiontree.model.utility.Dictionary;
import net.sourceforge.sqlexplorer.sqleditor.actions.SQLEditorToolBar;
import net.sourceforge.sqlexplorer.sqleditor.results.ResultsTab;
import net.sourceforge.sqlexplorer.sqlpanel.AbstractSQLExecution;
import net.sourceforge.sqlexplorer.util.PartAdapter2;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabFolder2Adapter;
import org.eclipse.swt.custom.CTabFolderEvent;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Sash;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IPropertyListener;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.EditorPart;
import org.eclipse.ui.part.FileEditorInput;
import org.talend.core.model.properties.PropertiesPackage;
import org.talend.core.model.properties.Property;

/**
 * SQLEditor is the top-level Editor component which is registered with Eclipse as the Editor for each new SQL
 * connection.
 * 
 * NOTE RE PREVIOUS VERSIONS: The previous version of this class was derived from TextEditor and "only" provided
 * facilities for editing and executing SQL text; the results were displayed in a separate view, where the results from
 * different editors would intermingle. Also, any messages output from the server (ie which was not a result set) were
 * lost.
 * 
 * This version includes a splitter pane where the top half is the text editor and the bottom half is a tabbed control
 * containing one tab per result set and a further tab for messages. The code in SQLEditor was becoming very large and
 * so the code for the text editor part has been split off into SQLTextEditor; it is largely unchanged except for the
 * refactoring.
 * 
 * Another change is that the editor no longer provides a status bar; because each query tab provides a status bar with
 * a message specific to the resultset, it was redundant to provide the same info (well, "most recent event" info) in a
 * status bar just 2 pixels below. However, the setMessage method is still supported and now updates the main Eclipse
 * status bar instead. This means that the "limit rows" feature moves to the Editor toolbar.
 * 
 * Behavoural Changes The editors now prompt to save when they're closed; this is part of the slight paradigm shift of
 * SQLExplorer - instead of the SQL window being a scratch pad for executing ad-hoc SQL, it's becoming part of a
 * development environment for stored procedures and queries.
 * 
 * @modified John Spackman
 * 
 */
public class SQLEditor extends EditorPart implements SwitchableSessionEditor {

    // The color of the sash
    private static final Color SASH_COLOR = IConstants.TAB_BORDER_COLOR;

    // Typical file extensions
    public static final String[] SUPPORTED_FILETYPES = new String[] { "*.sql", "*.txt", "*.*" };

    public static final String EDITOR_ID = "net.sourceforge.sqlexplorer.plugin.editors.SQLEditor";

    // The Session node from the Connections view
    private Session session;

    // Toolbar
    private SQLEditorToolBar toolBar;

    // The actual text editor for the top half of our composite editor
    private SQLTextEditor textEditor;

    // TabFolder for results and messages; note that we use CTabFolder and not TabFolder
    // because CTabFolder adds support for an "X" close button on each tab
    private CTabFolder tabFolder;

    // The messages tab
    private CTabItem messagesTab;

    // The Table in the Messages tab
    private Table messagesTable;

    // Our own dirty flag - used for new files
    private boolean isDirty;

    // True if the editor does not have a filename yet
    private boolean isUntitled;

    final static Color firstColor = new Color(null, 211, 225, 250);

    final static Color secondColor = new Color(null, 175, 201, 246);

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.ui.IEditorPart#init(org.eclipse.ui.IEditorSite, org.eclipse.ui.IEditorInput)
     */
    @Override
    public void init(IEditorSite site, IEditorInput input) throws PartInitException {

        // Configure the editor
        setSite(site);

        // PTODO qzhang fixed bug 3904: syschronize the resource.
        if (input instanceof FileEditorInput) {
            try {
                ((FileEditorInput) input).getFile().getParent().refreshLocal(IResource.DEPTH_ONE, null);
            } catch (CoreException e) {
                e.printStackTrace();
            }
        }
        setInput(input);

        // Create the text editor
        textEditor = new SQLTextEditor(this);
        textEditor.init(site, input);

        // setPartName(getSite().getPage().getLabel());

        // Make sure we get notification that our editor is closing because
        // we may need to stop running queries
        getSite().getPage().addPartListener(new PartAdapter2() {

            /*
             * (non-JavaDoc)
             * 
             * @see net.sourceforge.sqlexplorer.util.PartAdapter2#partClosed(org.eclipse.ui.IWorkbenchPartReference)
             */
            @Override
            public void partClosed(IWorkbenchPartReference partRef) {
                if (partRef.getPart(false) == SQLEditor.this) {
                    onCloseEditor();
                }
            }

        });

        // If we havn't got a view, then try for the current session in the ConnectionsView
        if (getSession() == null) {
            ConnectionsView view = SQLExplorerPlugin.getDefault().getConnectionsView();
            if (view != null) {
                User user = view.getDefaultUser();
                if (user != null) {
                    user.queueForNewSession(new SessionEstablishedAdapter() {

                        @Override
                        public void sessionEstablished(Session session) {
                            setSession(session);
                        }
                    });
                }
            }
        }
    }

    @Override
    public void dispose() {
        setSession(null);
        if (this.textEditor.getTitleImage() != null) {
            this.textEditor.getTitleImage().dispose();
        }
        super.dispose();
    }

    /*
     * (non-JavaDoc)
     * 
     * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
     */
    @Override
    public void createPartControl(Composite parent) {
        try {
            parent.setLayout(new FillLayout());

            // Create a wrapper for our stuff
            final Composite myParent = new Composite(parent, SWT.NONE);
            FormLayout layout = new FormLayout();
            myParent.setLayout(layout);
            FormData data;

            // Create sash and attach it to 75% of the way down
            final Sash sash = createSash(myParent);
            data = new FormData();
            // MOD scorreia 2008-10-30 enhance result tab height
            // data.top = new FormAttachment(75, 0);
            data.top = new FormAttachment(20, 0);
            data.left = new FormAttachment(0, 0);
            data.right = new FormAttachment(100, 0);
            sash.setLayoutData(data);

            // Create status bar and attach it to the bottom
            /*
             * Composite statusBar = createStatusBar(myParent); data = new FormData(); data.left = new FormAttachment(0,
             * 0); data.right = new FormAttachment(100, 0); data.bottom = new FormAttachment(100, 0);
             * statusBar.setLayoutData(data);
             */

            // Create the toolbar and attach it to the top of the composite
            createToolbar(myParent);
            data = new FormData();
            data.top = new FormAttachment(0, 0);
            data.left = new FormAttachment(0, 0);
            data.right = new FormAttachment(100, 0);
            toolBar.getToolbarControl().setLayoutData(data);

            // Attach the editor to the toolbar and the top of the sash
            final Composite editor = createEditor(myParent);
            data = new FormData();
            data.top = new FormAttachment(toolBar.getToolbarControl(), 0);
            data.bottom = new FormAttachment(sash, 0);
            data.left = new FormAttachment(0, 0);
            data.right = new FormAttachment(100, 0);
            editor.setLayoutData(data);

            // Attach the tabs to the bottom of the sash and the bottom of the composite
            CTabFolder tabFolder = createResultTabs(myParent);
            data = new FormData();
            data.top = new FormAttachment(sash, 0);
            data.left = new FormAttachment(0, 0);
            data.right = new FormAttachment(100, 0);
            data.bottom = new FormAttachment(100, 0);
            tabFolder.setLayoutData(data);

            if (session != null) {
                toolBar.onEditorSessionChanged(session);
            }

        } catch (Exception e) {
            SQLExplorerPlugin.error("Couldn't create text editor", e);
            MessageDialog.openError(getSite().getShell(), Messages.getString("SQLEditor.Init.CreateTextEditor"), e.getClass()
                    .getCanonicalName() + ": " + e.getMessage());
        }
    }

    /**
     * Creates the toolbar
     * 
     * @param parent
     */
    private void createToolbar(final Composite parent) {
        toolBar = new SQLEditorToolBar(parent, this);

        toolBar.addResizeListener(new ControlListener() {

            @Override
            public void controlMoved(ControlEvent e) {
            }

            @Override
            public void controlResized(ControlEvent e) {
                parent.getParent().layout(true);
                parent.layout(true);
            }
        });
    }

    /**
     * Creates the sash (the draggable splitter) between the editor and the results tab
     * 
     * @param parent
     * @return
     */
    private Sash createSash(Composite parent) {
        // Create the sash and put it in the middle
        final Sash sash = new Sash(parent, SWT.HORIZONTAL);
        sash.setBackground(SASH_COLOR);

        sash.addSelectionListener(new SelectionListener() {

            /*
             * (non-JavaDoc)
             * 
             * @see
             * org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(org.eclipse.swt.events.SelectionEvent)
             */
            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
                widgetSelected(e);
            }

            /*
             * (non-JavaDoc)
             * 
             * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
             */
            @Override
            public void widgetSelected(SelectionEvent e) {
                FormData data = (FormData) sash.getLayoutData();
                Rectangle rect = sash.getParent().getBounds();
                data.top = new FormAttachment(e.y, rect.height, 0);
                sash.getParent().layout();
                sash.getParent().getParent().layout();
            }
        });

        return sash;
    }

    /**
     * Creates the text editor in the top half
     * 
     * @param parent
     * @return
     */
    private Composite createEditor(Composite parent) {
        // Attach the editor to the top of the composite and the top of the sash
        final Composite editorParent = new Composite(parent, SWT.NONE);
        editorParent.setLayout(new FillLayout());
        textEditor.createPartControl(editorParent);
        textEditor.addPropertyListener(new IPropertyListener() {

            @Override
            public void propertyChanged(Object source, int propertyId) {
                SQLEditor.this.firePropertyChange(propertyId);
            }
        });

        return editorParent;
    }

    /**
     * Creates the results tabs in the bottom half
     * 
     * @param parent
     * @return
     */
    private CTabFolder createResultTabs(Composite parent) {
        tabFolder = new CTabFolder(parent, SWT.TOP | SWT.CLOSE);
        tabFolder.setBorderVisible(true);
        tabFolder.setLayoutData(new GridData(GridData.FILL_BOTH));

        // Set up a gradient background for the selected tab
        Display display = getSite().getShell().getDisplay();
        tabFolder.setSelectionBackground(new Color[] { display.getSystemColor(SWT.COLOR_WHITE), firstColor, secondColor,
                IConstants.TAB_BORDER_COLOR }, new int[] { 25, 50, 75 }, true);

        messagesTab = new CTabItem(tabFolder, SWT.NONE);
        messagesTab.setText(Messages.getString("SQLEditor.Results.Messages.Caption"));

        messagesTable = new Table(tabFolder, SWT.SINGLE | SWT.BORDER | SWT.FULL_SELECTION);
        messagesTab.setControl(messagesTable);
        messagesTable.setLinesVisible(true);
        messagesTable.setHeaderVisible(true);
        messagesTable.addSelectionListener(new SelectionAdapter() {

            @Override
            public void widgetSelected(SelectionEvent e) {
                super.widgetSelected(e);
                Message message = (Message) ((TableItem) e.item).getData();
                setCursorPosition(message.getLineNo(), message.getCharNo());
            }
        });

        TableColumn col = new TableColumn(messagesTable, SWT.NONE);
        col.setText(Messages.getString("SQLEditor.Results.Messages.Status"));
        col.pack();

        col = new TableColumn(messagesTable, SWT.NONE);
        col.setText(Messages.getString("SQLEditor.Results.Messages.Location"));
        col.pack();

        col = new TableColumn(messagesTable, SWT.NONE);
        col.setText(Messages.getString("SQLEditor.Results.Messages.SQL"));
        col.pack();

        col = new TableColumn(messagesTable, SWT.NONE);
        col.setText(Messages.getString("SQLEditor.Results.Messages.Text"));
        col.pack();

        tabFolder.setSelection(messagesTab);

        // Add a listener to get the close button on each tab
        tabFolder.addCTabFolder2Listener(new CTabFolder2Adapter() {

            /*
             * (non-JavaDoc)
             * 
             * @see org.eclipse.swt.custom.CTabFolder2Adapter#close(org.eclipse.swt.custom.CTabFolderEvent)
             */
            @Override
            public void close(CTabFolderEvent event) {
                super.close(event);
                CTabItem tabItem = (CTabItem) event.item;
                event.doit = onCloseTab(tabItem);
            }

        });

        return tabFolder;
    }

    /*
     * (non-JavaDoc)
     * 
     * @see org.eclipse.ui.part.EditorPart#setInput(org.eclipse.ui.IEditorInput)
     */
    @Override
    protected void setInput(IEditorInput input) {
        super.setInput(input);
        if (textEditor != null) {
            textEditor.setInput(input);
        }

        // Handle our own form of input
        if (input instanceof SQLEditorInput) {
            SQLEditorInput sqlInput = (SQLEditorInput) input;
            if (input != null) {
                User user = sqlInput.getUser();
                if (user != null) {
                    user.queueForNewSession(new SessionEstablishedAdapter() {

                        @Override
                        public void sessionEstablished(Session session) {
                            setSession(session);
                        }
                    });
                }
                isDirty = true;
                isUntitled = true;
            }
        }

        // set part name as displayName + " " + version
        String partName = input.getName();
        if (input instanceof FileEditorInput) {
            FileEditorInput fileEditorInput = (FileEditorInput) input;
            IPath fileName = fileEditorInput.getPath().removeFileExtension().addFileExtension("properties"); //$NON-NLS-1$

            Property property = null;
            URI propURI = URI.createFileURI(fileName.toOSString());
            Resource resource = new ResourceSetImpl().getResource(propURI, true);
            if (resource.getContents() != null) {
                Object object = EcoreUtil.getObjectByType(resource.getContents(), PropertiesPackage.eINSTANCE.getProperty());
                if (object != null) {
                    property = (Property) object;
                }
            }

            if (property != null) {
                partName = property.getDisplayName() + " " + property.getVersion(); //$NON-NLS-1$
            }
        }

        setPartName(partName);
    }

    /*
     * (non-JavaDoc)
     * 
     * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor)
     */
    @Override
    public void doSave(IProgressMonitor monitor) {
        if (monitor == null) {
            monitor = textEditor.getProgressMonitor();
        }

        if (isUntitled) {
            if (!doSave(false, monitor)) {
                monitor.setCanceled(true);
            }
            return;
        }

        // If it's a SQLEditorInput then we have to handle saving ourselves; once the file
        // has been saved into a project (via SaveAs) then the input becomes IFileEditorInput
        // and Eclipse knows how to deal with it
        IEditorInput input = getEditorInput();
        if (input instanceof SQLEditorInput) {
            try {
                SQLEditorInput sqlInput = (SQLEditorInput) input;
                saveToFile(sqlInput.getFile());
            } catch (IOException e) {
                SQLExplorerPlugin.error(e);
                monitor.setCanceled(true);
                return;
            }
        } else {
            textEditor.doSave(monitor);
        }

        setIsDirty(textEditor.isDirty());
    }

    /**
     * Save editor content to file.
     * 
     * @see org.eclipse.ui.ISaveablePart#doSaveAs()
     */
    @Override
    public void doSaveAs() {
        doSave(true, null);
    }

    /**
     * Implementation for save-as; returns true if successfull, false if not (i.e. the user cancelled the dialog)
     * 
     * @return true if saved, false if cancelled
     */
    public boolean doSave(boolean saveAs, IProgressMonitor monitor) {
        IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
        boolean haveProjects = projects != null && projects.length > 0;
        IEditorInput input = getEditorInput();

        boolean saveInsideProject = true;
        File file = null;
        if (input instanceof SQLEditorInput) {
            SQLEditorInput seInput = (SQLEditorInput) input;
            file = seInput.getFile();
        }

        // If we have a file, then we already have a filename outside of the project;
        // but if we're doing a save-as then recheck with the user
        if (file != null && !saveAs) {
            saveInsideProject = false;
        } else if (input instanceof SQLEditorInput) {
            IConstants.Confirm confirm = SQLExplorerPlugin.getConfirm(IConstants.CONFIRM_YNA_SAVING_INSIDE_PROJECT);

            // If we're supposed to ask the user...
            if (confirm == IConstants.Confirm.ASK) {
                // Build up the message to ask
                String msg = Messages.getString("Confirm.SaveInsideProject.Intro") + "\n\n";
                if (!haveProjects) {
                    msg = msg + Messages.getString("Confirm.SaveInsideProject.NoProjectsConfigured") + "\n\n";
                }
                msg = msg + Messages.getString("Confirm.SaveInsideProject.SaveInProject");

                // Ask them
                MessageDialogWithToggle dialog = MessageDialogWithToggle.openYesNoCancelQuestion(getSite().getShell(),
                        Messages.getString("SQLEditor.SaveAsDialog.Title"), msg,
                        Messages.getString("Confirm.SaveInsideProject.Toggle"), false, null, null);
                if (dialog.getReturnCode() == IDialogConstants.CANCEL_ID) {
                    return false;
                }

                // If they turned on the toggle ("Use this answer in the future"), update the preferences
                if (dialog.getToggleState()) {
                    confirm = dialog.getReturnCode() == IDialogConstants.YES_ID ? IConstants.Confirm.YES : IConstants.Confirm.NO;
                    SQLExplorerPlugin.getDefault().getPreferenceStore()
                            .setValue(IConstants.CONFIRM_YNA_SAVING_INSIDE_PROJECT, confirm.toString());
                }

                // Whether to save inside or outside
                saveInsideProject = dialog.getReturnCode() == IDialogConstants.YES_ID;
            } else {
                saveInsideProject = confirm == IConstants.Confirm.YES;
            }
        }

        // Saving inside a project - convert SQLEditorInput into a Resource by letting TextEditor do the work for us
        if (saveInsideProject) {
            if (!haveProjects) {
                MessageDialog.openError(getSite().getShell(), Messages.getString("Confirm.SaveInsideProject.Title"),
                        Messages.getString("Confirm.SaveInsideProject.CreateAProject"));
                return false;
            }
            if (input instanceof SQLEditorInput) {
                saveAs = true;
            }

            // Save it and use their EditorInput
            if (saveAs) {
                textEditor.doSaveAs();
            } else {
                if (monitor == null) {
                    monitor = textEditor.getProgressMonitor();
                }
                textEditor.doSave(monitor);
            }
            if (input.equals(textEditor.getEditorInput())) {
                return false;
            }
            input = textEditor.getEditorInput();
            setInput(input);

            // Update the display
            setPartName(input.getName());
            setTitleToolTip(input.getToolTipText());

        } else {
            try {
                if (file == null || saveAs) {
                    FileDialog dialog = new FileDialog(getSite().getShell(), SWT.SAVE);
                    dialog.setText(Messages.getString("SQLEditor.SaveAsDialog.Title"));
                    dialog.setFilterExtensions(SUPPORTED_FILETYPES);
                    dialog.setFilterNames(SUPPORTED_FILETYPES);
                    dialog.setFileName("*.sql");

                    String path = dialog.open();
                    if (path == null) {
                        return false;
                    }
                    file = new File(path);
                }

                // Save it
                saveToFile(file);

                // Update the editor input
                input = new SQLEditorInput(file);
                setInput(input);
                setPartName(input.getName());
                setTitleToolTip(input.getToolTipText());

            } catch (IOException e) {
                SQLExplorerPlugin.error("Couldn't save sql", e);
                MessageDialog.openError(getSite().getShell(), Messages.getString("SQLEditor.SaveAsDialog.Error"), e.getMessage());
                return false;
            }

        }

        setIsDirty(textEditor.isDirty());
        return true;
    }

    /**
     * Loads a file into the editor
     * 
     * @param file file to load
     */
    /*
     * private void loadFromFile(File file) throws IOException { BufferedReader reader = null;
     * 
     * StringBuffer all = new StringBuffer((int)file.length()); String str = null; //String delimiter =
     * _editor.getSqlTextViewer().getTextWidget().getLineDelimiter(); // Note: I have changed the delimiter to a
     * hardcoded \n because this a) allows the // interface to SQLEditor to be cleaner (see SQLEditor for refactoring
     * description) // and I can find several other places where text will be passed to the same text // editor and \n
     * is hard coded. If there is an issue with how the view encodes // line delimiters, it is likely to be a global
     * problem and we should handle it in // SQLEditor.setText() instead. // reader = new BufferedReader(new
     * FileReader(file)); try { while ((str = reader.readLine()) != null) { all.append(str); all.append('\n'); }
     * 
     * setText(all.toString()); isDirty = false; } finally { reader.close(); } }
     */

    /**
     * Saves the text to a file on the filing system - IE outside of any projects
     */
    private void saveToFile(File file) throws IOException {
        if (file.exists()) {
            file.delete();
        }

        file.createNewFile();

        String content = textEditor.sqlTextViewer.getDocument().get();

        // MOD sizhaoliu 2012-04-02 for TDQ-5070 Encoding issue with saving generated sql query action
        // BufferedWriter writer = new BufferedWriter(new FileWriter(file));
        // writer.write(content, 0, content.length());
        // writer.close();
        FileOutputStream fos = new FileOutputStream(file);
        fos.write(content.getBytes(Charset.forName("UTF-8")));
        fos.close();

        // PTODO rli fixed feature 5186: synchronized the resource.
        IFile[] findFile = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(file.toURI());
        if (findFile == null) {
            return;
        }
        try {
            findFile[0].getParent().refreshLocal(IResource.DEPTH_ONE, null);
        } catch (CoreException e) {
            e.printStackTrace();
        }
    }

    /**
     * Adds a message to the message window
     * 
     * @param message
     */
    public void addMessage(Message message) {
        if (isClosed()) {
            return;
        }

        // Don't log success messages unless we're supposed to
        if (message.getStatus() == Message.Status.SUCCESS
                && !SQLExplorerPlugin.getDefault().getPreferenceStore().getBoolean(IConstants.LOG_SUCCESS_MESSAGES)) {
            return;
        }

        TableItem tableRow = new TableItem(messagesTable, SWT.NONE);
        tableRow.setText(message.getTableText());
        tableRow.setData(message);

        TableColumn[] cols = messagesTable.getColumns();
        for (TableColumn col : cols) {
            col.pack();
        }
    }

    /**
     * Allocates a new tab for an AbstractSQLExecution; the tab and it's contents are completely undefined
     * 
     * @param sqlExec
     * @return
     */
    public ResultsTab createResultsTab(AbstractSQLExecution job) {
        if (tabFolder.isDisposed()) {
            return null;
        }

        // Create the new tab, make it second to last (IE keep the messages tab
        // always at the end) and set the new tab's title to the 1-based index
        CTabItem tabItem = new CTabItem(tabFolder, SWT.CLOSE, tabFolder.getItems().length - 1);
        tabItem.setText(Integer.toString(tabFolder.getItems().length - 1));
        tabFolder.setSelection(tabItem);

        // Make sure we can track the execution
        tabItem.setData(job);

        // Create a composite to add all controls to
        Composite composite = new Composite(tabFolder, SWT.NONE);
        tabItem.setControl(composite);

        // Make sure we're visible
        getSite().getPage().bringToTop(this);

        return new ResultsTab(this, tabItem, composite);
    }

    public boolean isClosed() {
        return tabFolder.isDisposed();
    }

    /**
     * Called internally when the user tries to close a tab
     * 
     * @param tabItem
     * @return true if the tab should be closed
     */
    private boolean onCloseTab(CTabItem tabItem) {
        // Cannot close the messages tab
        if (tabItem == messagesTab) {
            return false;
        }

        // The SQL query runs in a background thread and if the tab item gets disposed of
        // BEFORE the query has finished we need to notify the thread that there is no UI
        // left and that the thread should terminate as soon as possible.
        synchronized (this) {
            // Get the SQL execution
            AbstractSQLExecution job = getSqlExecution(tabItem);

            // Stop the statement - but allow it to happen asynchronously
            if (job != null) {
                tabItem.setData(null);
                job.cancel();
            }
        }

        return true;
    }

    /**
     * Called internally when the user tries to close the editor
     */
    private void onCloseEditor() {
        textEditor.getDocumentProvider().disconnect(getEditorInput());
        textEditor.setInput(null);
        clearResults();

        // ADD msjian TDQ-5952: we should close connections always
        // when close the SQLEditor, close the connection
        if (session != null) {
            List<SQLConnection> connections = session.getUser().getUnusedConnections();
            for (SQLConnection sqlConn : connections) {
                session.getUser().releaseFromPool(sqlConn);
            }
        }
        // TDQ-5952~
    }

    /**
     * Closes all result tabs and signals all associated AbstractSQLExecutions to stop (if they're still running)
     */
    public void clearResults() {
        if (tabFolder.isDisposed()) {
            return;
        }
        synchronized (this) {
            CTabItem[] tabItems = tabFolder.getItems();
            for (CTabItem tabItem : tabItems) {
                if (tabItem != messagesTab) {
                    closeTab(tabItem);
                }
            }
            messagesTable.removeAll();
        }
    }

    /**
     * Removes a tab from the results tabs, stopping any pending executions etc
     * 
     * @param tab
     */
    private void closeTab(CTabItem tab) {
        tab.dispose();
    }

    /**
     * Converts a TabItem into the AbstractSQLExecution for that tab
     * 
     * @param tab
     * @return
     */
    public AbstractSQLExecution getSqlExecution(CTabItem tab) {
        return (AbstractSQLExecution) tab.getData();
    }

    /**
     * Converts an AbstractSQLExecution into its TabItem
     * 
     * @param sqlExec
     * @return
     */
    public CTabItem getResultsTab(AbstractSQLExecution sqlExec) {
        CTabItem[] items = tabFolder.getItems();
        for (CTabItem item : items) {
            if (item.getData() == sqlExec) {
                return item;
            }
        }
        return null;
    }

    /*
     * (non-JavaDoc)
     * 
     * @see org.eclipse.ui.part.EditorPart#isDirty()
     */
    @Override
    public boolean isDirty() {
        boolean saveOnClose = SQLExplorerPlugin.getDefault().getPreferenceStore()
                .getBoolean(IConstants.REQUIRE_SAVE_ON_CLOSE_EDITOR);
        return saveOnClose && (isDirty || textEditor.isDirty());
    }

    protected void setIsDirty(boolean isDirty) {
        if (this.isDirty != isDirty) {
            this.isDirty = isDirty;
            firePropertyChange(PROP_DIRTY);
        }
        if (!isDirty) {
            isUntitled = false;
        }
    }

    /*
     * (non-JavaDoc)
     * 
     * @see org.eclipse.ui.part.WorkbenchPart#setFocus()
     */
    @Override
    public void setFocus() {
        textEditor.setFocus();
    }

    /*
     * (non-JavaDoc)
     * 
     * @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed()
     */
    @Override
    public boolean isSaveAsAllowed() {
        return textEditor.isSaveAsAllowed();
    }

    /**
     * Sets the message displayed on the Eclipse main status bar
     * 
     * @param s
     */
    public void setMessage(String s) {
        IStatusLineManager manager = getEditorSite().getActionBars().getStatusLineManager();
        manager.setMessage(s);
        // statusMgr.setMessage(s);
    }

    /**
     * Sets the dictionary used by the text editor
     * 
     * @param dictionary
     */
    public void setNewDictionary(final Dictionary dictionary) {
        textEditor.setNewDictionary(dictionary);
    }

    /**
     * Sets the session to use for executing queries
     * 
     * @param session The new Session
     */
    @Override
    public void setSession(Session session) {
        if (session == this.session) {
            return;
        }

        // If we already have a session and we're changing to a different one, close the current one
        if (getSession() != null && session != this.session) {
            this.session.close();
        }
        this.session = session;
        if (textEditor != null) {
            textEditor.onEditorSessionChanged(session);
        }
        if (toolBar != null) {
            toolBar.onEditorSessionChanged(session);
        }
    }

    /**
     * @return the session
     */
    @Override
    public Session getSession() {
        // In theory, if our session is somehow closed by something else then we will have already
        // had our session changed or reset; however, just in case this doesn't happen we can
        // detect it because the session has it's user set to null when it is detached. If that
        // happened, then we reset the session to null
        if (session != null && session.getUser() == null) {
            session = null;
        }
        return session;
    }

    /**
     * Sets the text of the editor
     * 
     * @param txt
     */
    public void setText(String txt) {
        IDocument dc = new Document(txt);
        textEditor.sqlTextViewer.setDocument(dc);
        textEditor.sqlTextViewer.refresh();
    }

    /**
     * Returns the text to be executed; this is the entire text if there is no selection, else just the selected text
     * 
     * @return
     */
    public String getSQLToBeExecuted() {

        String sql = textEditor.sqlTextViewer.getTextWidget().getSelectionText();
        if (sql == null || sql.trim().length() == 0) {
            sql = textEditor.sqlTextViewer.getTextWidget().getText();
        }

        // Normalise this to have standard \n in strings. \r confuses Oracle and
        // isn't normally needed internally anyway
        StringBuffer sb = new StringBuffer(sql);
        for (int i = 0; i < sb.length(); i++) {
            if (sb.charAt(i) == '\r') {
                sb.deleteCharAt(i);
                i--;
            }
        }
        sql = sb.toString();

        return sql != null ? sql : "";
    }

    /**
     * returns the line number that the SQL starts on
     * 
     * @return
     */
    public int getSQLLineNumber() {
        String sql = textEditor.sqlTextViewer.getTextWidget().getSelectionText();
        if (sql == null || sql.trim().length() == 0) {
            return 1;
        }
        Point pt = textEditor.sqlTextViewer.getTextWidget().getSelection();
        if (pt == null) {
            return 1;
        }
        StyledText text = (StyledText) textEditor.getAdapter(org.eclipse.swt.widgets.Control.class);
        int offset = pt.x;
        int lineNo = text.getLineAtOffset(offset);
        return lineNo + 1;
    }

    /**
     * Clears the text of the editor
     */
    public void clearText() {
        textEditor.sqlTextViewer.clearText();
    }

    /**
     * Returns the toolbar
     * 
     * @return
     */
    public SQLEditorToolBar getEditorToolBar() {
        return toolBar;
    }

    /**
     * Returns whether to limit the results and if so by how much.
     * 
     * @return the maximum number of rows to retrieve, 0 for unlimited, or null if it cannot be interpretted
     */
    public Integer getLimitResults() {
        return toolBar.getLimitResults();
    }

    /**
     * Updates the cursor position info in the status bar
     */
    public void updateCursorPosition() {
        Object adapter = textEditor.getAdapter(org.eclipse.swt.widgets.Control.class);
        if (adapter instanceof StyledText) {
            StyledText text = (StyledText) adapter;
            int offset = text.getCaretOffset();
            int lineNo = text.getLineAtOffset(offset);
            int lineOffset = text.getOffsetAtLine(lineNo);
            int charNo = offset - lineOffset;

            IStatusLineManager manager = getEditorSite().getActionBars().getStatusLineManager();
            IContributionItem items[] = manager.getItems();
            for (IContributionItem item : items) {
                if (item instanceof CursorPositionContrib) {
                    CursorPositionContrib contrib = (CursorPositionContrib) item;
                    contrib.setPosition(lineNo + 1, charNo + 1);
                    break;
                }
            }
        }
    }

    /**
     * Moves the text cursor to a given line and column
     * 
     * @param lineNo
     * @param charNo
     */
    public void setCursorPosition(int lineNo, int charNo) {
        if (lineNo < 1) {
            return;
        }
        if (charNo < 1) {
            charNo = 1;
        }
        Object adapter = textEditor.getAdapter(org.eclipse.swt.widgets.Control.class);
        if (adapter instanceof StyledText) {
            StyledText text = (StyledText) adapter;
            // ADD xqliu 2012-07-24 TDQ-5853 forced to navigate to the first row
            lineNo = 1;
            // ~ TDQ-5853
            int lineOffset = text.getOffsetAtLine(lineNo - 1);
            text.setCaretOffset(lineOffset + charNo - 1);
            updateCursorPosition();
            text.setFocus();
            text.showSelection();
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see net.sourceforge.sqlexplorer.plugin.editors.SwitchableSessionEditor#refreshToolbars()
     */
    @Override
    public void refreshToolbars() {
        getEditorToolBar().refresh();
    }

    /**
     * Added TDQ-7532, 20130719 yyin: to lock the editor make it not editable/or editable
     * 
     * @param lock : true - set it editable; false - set it not editable.
     */
    public void setEditable(boolean lock) {
        this.textEditor.getViewer().setEditable(lock);
        this.toolBar.getToolbarControl().setEnabled(lock);
    }
}
